/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the GLQuake source code.

 The GLQuake source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The GLQuake source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the GLQuake source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// win_input.cpp - DirectInput and XInput management
//


#include "../client/client.h"
#include "win_local.h"


#define DIRECTINPUT_VERSION			0x0800
#define DIRECTINPUT_DLL				"dinput8.dll"

#include "../include/DirectX/dinput.h"
#include "../include/DirectX/xinput.h"


#define MAX_CONTROLLERS					8

struct controller_t {
	char					name[64];
	bool					x360;

	int						numAxes;
	int						numPOVs;
	int						numButtons;

	GUID					guid;
	DWORD					userIndex;
};

static controller_t			in_controllers[MAX_CONTROLLERS];
static int					in_numControllers;


/*
 ==============================================================================

 DIRECTINPUT

 ==============================================================================
*/

#define MAX_BUFFERED_EVENTS				256

typedef HRESULT				(WINAPI * DIRECTINPUT8CREATE)(HINSTANCE, DWORD, REFIID, LPVOID *, LPUNKNOWN);

static HRESULT				(WINAPI * pDirectInput8Create)(HINSTANCE hInst, DWORD dwVersion, REFIID riidltf, LPVOID *ppvOut, LPUNKNOWN pUnkOuter);

static LPDIRECTINPUT8		in_diObject;
static HMODULE				in_diLibrary;
static bool					in_diInitialized;


/*
 ==================
 IN_EnumDevicesProc
 ==================
*/
static BOOL CALLBACK IN_EnumDevicesProc (const DIDEVICEINSTANCE *pDDI, VOID *pContext){

	controller_t			*controller;
	LPDIRECTINPUTDEVICE8	device;
	DIDEVCAPS				diDevCaps;
	HRESULT					result;

	// Try to create the device
	result = in_diObject->CreateDevice(pDDI->guidInstance, &device, NULL);
	if (FAILED(result))
		return DIENUM_CONTINUE;

	// Try to get the device capabilities
	diDevCaps.dwSize = sizeof(DIDEVCAPS);

	result = device->GetCapabilities(&diDevCaps);
	if (FAILED(result)){
		device->Release();
		return DIENUM_CONTINUE;
	}

	// Release the device
	device->Release();

	// Add it
	if (in_numControllers == MAX_CONTROLLERS)
		return DIENUM_STOP;

	controller = &in_controllers[in_numControllers++];

	// Fill it in
	Str_Copy(controller->name, pDDI->tszProductName, sizeof(controller->name));
	controller->x360 = false;
	controller->numAxes = diDevCaps.dwAxes;
	controller->numPOVs = diDevCaps.dwPOVs;
	controller->numButtons = diDevCaps.dwButtons;
	controller->guid = pDDI->guidInstance;

	// Increment counter
	*(int *)pContext = *(int *)pContext + 1;

	return DIENUM_CONTINUE;
}

/*
 ==================
 IN_InitDirectInput
 ==================
*/
static void IN_InitDirectInput (){

	HRESULT	result;
	int		count = 0;

	Com_Printf("Initializing DirectInput\n");

	// Load the DirectInput DLL
	Com_Printf("...calling LoadLibrary( '%s' ): ", DIRECTINPUT_DLL);

	if ((in_diLibrary = LoadLibrary(DIRECTINPUT_DLL)) == NULL){
		Com_Printf("failed\n");
		return;
	}

	Com_Printf("succeeded\n");

	// Get the function pointer
	if ((pDirectInput8Create = (DIRECTINPUT8CREATE)GetProcAddress(in_diLibrary, "DirectInput8Create")) == NULL){
		Com_Printf("...failed to get 'DirectInput8Create' procedure address\n");

		FreeLibrary(in_diLibrary);
		in_diLibrary = NULL;

		return;
	}

	// Create the DirectInput object
	Com_Printf("...creating DirectInput object: ");

	result = pDirectInput8Create(sys.hInstance, DIRECTINPUT_VERSION, IID_IDirectInput8, (LPVOID *)&in_diObject, NULL);
	if (FAILED(result)){
		Com_Printf("failed\n");

		FreeLibrary(in_diLibrary);
		in_diLibrary = NULL;

		return;
	}

	Com_Printf("succeeded\n");

	// Check if a mouse is available
	result = in_diObject->GetDeviceStatus(GUID_SysMouse);
	if (SUCCEEDED(result))
		Com_Printf("...mouse found\n");
	else
		Com_Printf("...no mouse found\n");

	// Enumerate available controllers
	in_diObject->EnumDevices(DI8DEVCLASS_GAMECTRL, IN_EnumDevicesProc, &count, DIEDFL_ATTACHEDONLY);
	if (count)
		Com_Printf("...%i controllers found\n", count);
	else
		Com_Printf("...no controllers found\n");

	// DirectInput fully initialized
	in_diInitialized = true;
}

/*
 ==================
 IN_ShutdownDirectInput
 ==================
*/
static void IN_ShutdownDirectInput (){

	if (!in_diInitialized)
		return;

	Com_Printf("Shutting down DirectInput\n");

	if (in_diObject){
		Com_Printf("...releasing DirectInput object\n");

		in_diObject->Release();
		in_diObject = NULL;
	}

	if (in_diLibrary){
		Com_Printf("...unloading DirectInput DLL\n");

		FreeLibrary(in_diLibrary);
		in_diLibrary = NULL;
	}

	in_diInitialized = false;
}


/*
 ==============================================================================

 DIRECTINPUT MOUSE

 ==============================================================================
*/

#define DI_MOUSE_MAX_AXES				3
#define DI_MOUSE_MAX_BUTTONS			8

#define DI_MOUSE_NUM_OBJECTS			(DI_MOUSE_MAX_AXES + DI_MOUSE_MAX_BUTTONS)

#define DI_MOUSE_DATA_SIZE				(DI_MOUSE_MAX_AXES * sizeof(DWORD) + DI_MOUSE_MAX_BUTTONS * sizeof(BYTE))

struct diMouse_t {
	bool					initialized;
	bool					active;

	LPDIRECTINPUTDEVICE8	device;

	int						numAxes;
	int						numButtons;
};

static DIOBJECTDATAFORMAT	in_diMouseObjects[] = {
	{&GUID_XAxis,	0,	DIDFT_AXIS | DIDFT_ANYINSTANCE,						0},
	{&GUID_YAxis,	4,	DIDFT_AXIS | DIDFT_ANYINSTANCE,						0},
	{&GUID_ZAxis,	8,	DIDFT_AXIS | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	12,	DIDFT_BUTTON | DIDFT_ANYINSTANCE,					0},
	{&GUID_Button,	13,	DIDFT_BUTTON | DIDFT_ANYINSTANCE,					0},
	{&GUID_Button,	14,	DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	15,	DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	16,	DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	17,	DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	18,	DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	19,	DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0}
};

static diMouse_t			in_diMouse;


/*
 ==================
 IN_ClearDIMouseState
 ==================
*/
static void IN_ClearDIMouseState (){

	int		i;

	if (!in_diMouse.initialized)
		return;

	// Clear movement
	IN_ClearMouseMoves();

	// Clear button state
	for (i = 0; i < in_diMouse.numButtons; i++){
		if (!Key_IsDown(K_MOUSE1 + i))
			continue;

		Key_Event(K_MOUSE1 + i, 0, false);
	}
}

/*
 ==================
 IN_ActivateDIMouse
 ==================
*/
static void IN_ActivateDIMouse (){

	HRESULT	result;

	if (!in_diMouse.initialized)
		return;

	if (in_diMouse.active)
		return;
	in_diMouse.active = true;

	// Set the cooperative level and acquire the device
	result = in_diMouse.device->SetCooperativeLevel(sys.hWndMain, DISCL_EXCLUSIVE | DISCL_FOREGROUND);

	if (SUCCEEDED(result))
		result = in_diMouse.device->Acquire();

	if (FAILED(result))
		Com_DPrintf(S_COLOR_RED "Couldn't activate mouse device\n");
}

/*
 ==================
 IN_DeactivateDIMouse
 ==================
*/
static void IN_DeactivateDIMouse (){

	if (!in_diMouse.initialized)
		return;

	if (!in_diMouse.active)
		return;
	in_diMouse.active = false;

	// Clear state
	IN_ClearDIMouseState();

	// Unacquire the device
	in_diMouse.device->Unacquire();
}

/*
 ==================
 IN_PollDIMouse
 ==================
*/
static void IN_PollDIMouse (){

	DIDEVICEOBJECTDATA	diDeviceData[MAX_BUFFERED_EVENTS];
	DWORD				numEvents = MAX_BUFFERED_EVENTS;
	HRESULT				result;
	int					x = 0, y = 0;
	int					delta;
	int					i;

	if (!in_diMouse.initialized)
		return;

	// Temporarily deactivate if we don't have the focus
	if (!sys.hWndMain || !sys.isActive){
		IN_DeactivateDIMouse();
		return;
	}

	// Temporarily deactivate if we're in windowed mode and the console is down
	if (!sys.isFullscreen && (Key_GetCatchers() & KEYCATCH_CONSOLE)){
		IN_DeactivateDIMouse();
		return;
	}

	// Activate if needed
	IN_ActivateDIMouse();

	// Poll the device
	result = in_diMouse.device->Poll();

	if (SUCCEEDED(result))
		result = in_diMouse.device->GetDeviceData(sizeof(DIDEVICEOBJECTDATA), diDeviceData, &numEvents, 0);

	if (FAILED(result)){
		Com_DPrintf(S_COLOR_RED "Couldn't poll mouse device\n");

		if (result == DIERR_NOTACQUIRED || result == DIERR_INPUTLOST){
			in_diMouse.device->SetCooperativeLevel(sys.hWndMain, DISCL_EXCLUSIVE | DISCL_FOREGROUND);

			in_diMouse.device->Acquire();
		}

		return;
	}

	// Generate events
	for (i = 0; i < numEvents; i++){
		switch (diDeviceData[i].dwOfs){
		case DIMOFS_X:
			x += diDeviceData[i].dwData;

			break;
		case DIMOFS_Y:
			y += diDeviceData[i].dwData;

			break;
		case DIMOFS_Z:
			delta = diDeviceData[i].dwData;

			if (delta > 0){
				Key_Event(K_MWHEELUP, diDeviceData[i].dwTimeStamp, true);
				Key_Event(K_MWHEELUP, diDeviceData[i].dwTimeStamp, false);
			}
			else if (delta < 0){
				Key_Event(K_MWHEELDOWN, diDeviceData[i].dwTimeStamp, true);
				Key_Event(K_MWHEELDOWN, diDeviceData[i].dwTimeStamp, false);
			}

			break;
		case DIMOFS_BUTTON0:
			if (diDeviceData[i].dwData & 0x80)
				Key_Event(K_MOUSE1, diDeviceData[i].dwTimeStamp, true);
			else
				Key_Event(K_MOUSE1, diDeviceData[i].dwTimeStamp, false);

			break;
		case DIMOFS_BUTTON1:
			if (diDeviceData[i].dwData & 0x80)
				Key_Event(K_MOUSE2, diDeviceData[i].dwTimeStamp, true);
			else
				Key_Event(K_MOUSE2, diDeviceData[i].dwTimeStamp, false);

			break;
		case DIMOFS_BUTTON2:
			if (diDeviceData[i].dwData & 0x80)
				Key_Event(K_MOUSE3, diDeviceData[i].dwTimeStamp, true);
			else
				Key_Event(K_MOUSE3, diDeviceData[i].dwTimeStamp, false);

			break;
		case DIMOFS_BUTTON3:
			if (diDeviceData[i].dwData & 0x80)
				Key_Event(K_MOUSE4, diDeviceData[i].dwTimeStamp, true);
			else
				Key_Event(K_MOUSE4, diDeviceData[i].dwTimeStamp, false);

			break;
		case DIMOFS_BUTTON4:
			if (diDeviceData[i].dwData & 0x80)
				Key_Event(K_MOUSE5, diDeviceData[i].dwTimeStamp, true);
			else
				Key_Event(K_MOUSE5, diDeviceData[i].dwTimeStamp, false);

			break;
		case DIMOFS_BUTTON5:
			if (diDeviceData[i].dwData & 0x80)
				Key_Event(K_MOUSE6, diDeviceData[i].dwTimeStamp, true);
			else
				Key_Event(K_MOUSE6, diDeviceData[i].dwTimeStamp, false);

			break;
		case DIMOFS_BUTTON6:
			if (diDeviceData[i].dwData & 0x80)
				Key_Event(K_MOUSE7, diDeviceData[i].dwTimeStamp, true);
			else
				Key_Event(K_MOUSE7, diDeviceData[i].dwTimeStamp, false);

			break;
		case DIMOFS_BUTTON7:
			if (diDeviceData[i].dwData & 0x80)
				Key_Event(K_MOUSE8, diDeviceData[i].dwTimeStamp, true);
			else
				Key_Event(K_MOUSE8, diDeviceData[i].dwTimeStamp, false);

			break;
		}
	}

	if (!x && !y)
		return;

	IN_MouseMoveEvent(x, y);
}

/*
 ==================
 IN_InitDIMouse
 ==================
*/
static void IN_InitDIMouse (){

	DIDEVCAPS		diDevCaps;
	DIDATAFORMAT	diDataFormat = {sizeof(DIDATAFORMAT), sizeof(DIOBJECTDATAFORMAT), DIDF_RELAXIS, DI_MOUSE_DATA_SIZE, DI_MOUSE_NUM_OBJECTS, in_diMouseObjects};
	DIPROPDWORD		diPropDWord = {{sizeof(DIPROPDWORD), sizeof(DIPROPHEADER), 0, DIPH_DEVICE}, MAX_BUFFERED_EVENTS};
	HRESULT			result;

	if (!in_diInitialized){
		Com_Printf("...mouse input is not available\n");
		return;
	}

	// Create the device
	result = in_diObject->CreateDevice(GUID_SysMouse, &in_diMouse.device, NULL);
	if (FAILED(result)){
		Com_Printf("...couldn't create mouse device\n");
		return;
	}

	// Get the device capabilities
	diDevCaps.dwSize = sizeof(DIDEVCAPS);

	result = in_diMouse.device->GetCapabilities(&diDevCaps);
	if (FAILED(result)){
		Com_Printf("...couldn't get mouse device capabilities\n");

		in_diMouse.device->Release();
		in_diMouse.device = NULL;

		return;
	}

	// Set the data format
	result = in_diMouse.device->SetDataFormat(&diDataFormat);
	if (FAILED(result)){
		Com_Printf("...couldn't set mouse device data format\n");

		in_diMouse.device->Release();
		in_diMouse.device = NULL;

		return;
	}

	// Set the buffer size
	result = in_diMouse.device->SetProperty(DIPROP_BUFFERSIZE, &diPropDWord.diph);
	if (FAILED(result)){
		Com_Printf("...couldn't set mouse device buffer size\n");

		in_diMouse.device->Release();
		in_diMouse.device = NULL;

		return;
	}

	// Set the cooperative level
	result = in_diMouse.device->SetCooperativeLevel(sys.hWndMain, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
	if (FAILED(result))
		Com_DPrintf("...couldn't set mouse device cooperative level\n");

	// Acquire the device
	result = in_diMouse.device->Acquire();
	if (FAILED(result))
		Com_DPrintf("...couldn't acquire mouse device\n");

	// The mouse device is now fully initialized
	in_diMouse.initialized = true;
	in_diMouse.active = true;

	in_diMouse.numAxes = diDevCaps.dwAxes;
	if (in_diMouse.numAxes > DI_MOUSE_MAX_AXES)
		in_diMouse.numAxes = DI_MOUSE_MAX_AXES;

	in_diMouse.numButtons = diDevCaps.dwButtons;
	if (in_diMouse.numButtons > DI_MOUSE_MAX_BUTTONS)
		in_diMouse.numButtons = DI_MOUSE_MAX_BUTTONS;

	Com_Printf("...mouse initialized (%i axes, %i buttons)\n", in_diMouse.numAxes, in_diMouse.numButtons);
}

/*
 ==================
 IN_ShutdownDIMouse
 ==================
*/
static void IN_ShutdownDIMouse (){

	if (!in_diMouse.initialized)
		return;

	// Clear state
	IN_ClearDIMouseState();

	// Unacquire and release the device
	if (in_diMouse.device){
		in_diMouse.device->Unacquire();
		in_diMouse.device->Release();
		in_diMouse.device = NULL;
	}

	Mem_Fill(&in_diMouse, 0, sizeof(diMouse_t));
}


/*
 ==============================================================================

 DIRECTINPUT CONTROLLER

 ==============================================================================
*/

#define DI_CONTROLLER_MAX_AXES			8
#define DI_CONTROLLER_MAX_POVS			4
#define DI_CONTROLLER_MAX_BUTTONS		32

#define DI_CONTROLLER_NUM_OBJECTS		(DI_CONTROLLER_MAX_AXES + DI_CONTROLLER_MAX_POVS + DI_CONTROLLER_MAX_BUTTONS)

#define DI_CONTROLLER_DATA_SIZE			(DI_CONTROLLER_MAX_AXES * sizeof(DWORD) + DI_CONTROLLER_MAX_POVS * sizeof(DWORD) + DI_CONTROLLER_MAX_BUTTONS * sizeof(BYTE))

struct diController_t {
	bool					initialized;
	bool					active;

	LPDIRECTINPUTDEVICE8	device;

	int						numAxes;
	int						numPOVs;
	int						numButtons;

	int						povState[DI_CONTROLLER_MAX_POVS];
	int						buttonState;
};

static DIOBJECTDATAFORMAT	in_diControllerObjects[] = {
	{&GUID_XAxis,	0,	DIDFT_AXIS | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_YAxis,	4,	DIDFT_AXIS | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_ZAxis,	8,	DIDFT_AXIS | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_RxAxis,	12,	DIDFT_AXIS | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_RyAxis,	16,	DIDFT_AXIS | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_RzAxis,	20,	DIDFT_AXIS | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Slider,	24,	DIDFT_AXIS | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Slider,	28,	DIDFT_AXIS | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_POV,		32, DIDFT_POV | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,		0},
	{&GUID_POV,		36,	DIDFT_POV | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,		0},
	{&GUID_POV,		40,	DIDFT_POV | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,		0},
	{&GUID_POV,		44,	DIDFT_POV | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,		0},
	{&GUID_Button,	48, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	49, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	50, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	51, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	52, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	53, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	54, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	55, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	56, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	57, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	58, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	59, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	60, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	61, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	62, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	63, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	64, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	65, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	66, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	67, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	68, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	69, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	70, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	71, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	72, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	73, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	74, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	75, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	76, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	77, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	78, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0},
	{&GUID_Button,	79, DIDFT_BUTTON | DIDFT_ANYINSTANCE | DIDFT_OPTIONAL,	0}
};

static diController_t		in_diController;


/*
 ==================
 IN_ClearDIControllerState
 ==================
*/
static void IN_ClearDIControllerState (){

	int		i, j;

	if (!in_diController.initialized)
		return;

	// Clear axes
	IN_ClearControllerAxes();

	// Clear POV state
	for (i = 0; i < in_diController.numPOVs; i++){
		for (j = 0; j < 4; j++){
			if (!(in_diController.povState[i] & BIT(j)))
				continue;

			Key_Event(K_CTRL_POV1 + (i << 2) + j, 0, false);
		}

		in_diController.povState[i] = 0;
	}

	// Clear button state
	for (i = 0; i < in_diController.numButtons; i++){
		if (!(in_diController.buttonState & BIT(i)))
			continue;

		Key_Event(K_CTRL_BUTTON1 + i, 0, false);
	}

	in_diController.buttonState = 0;
}

/*
 ==================
 IN_ActivateDIController
 ==================
*/
static void IN_ActivateDIController (){

	HRESULT	result;

	if (!in_diController.initialized)
		return;

	if (in_diController.active)
		return;
	in_diController.active = true;

	// Set the cooperative level and acquire the device
	result = in_diController.device->SetCooperativeLevel(sys.hWndMain, DISCL_EXCLUSIVE | DISCL_FOREGROUND);

	if (SUCCEEDED(result))
		result = in_diController.device->Acquire();

	if (FAILED(result))
		Com_DPrintf(S_COLOR_RED "Couldn't activate controller device\n");
}

/*
 ==================
 IN_DeactivateDIController
 ==================
*/
static void IN_DeactivateDIController (){

	if (!in_diController.initialized)
		return;

	if (!in_diController.active)
		return;
	in_diController.active = false;

	// Clear state
	IN_ClearDIControllerState();

	// Unacquire the device
	in_diController.device->Unacquire();
}

/*
 ==================
 IN_PollDIController
 ==================
*/
static void IN_PollDIController (){

	DIJOYSTATE	diDeviceState;
	HRESULT		result;
	int			state;
	int			i, j;

	if (!in_diController.initialized)
		return;

	// Temporarily deactivate if we don't have the focus
	if (!sys.hWndMain || !sys.isActive){
		IN_DeactivateDIController();
		return;
	}

	// Activate if needed
	IN_ActivateDIController();

	// Poll the device
	result = in_diController.device->Poll();

	if (SUCCEEDED(result))
		result = in_diController.device->GetDeviceState(sizeof(DIJOYSTATE), &diDeviceState);

	if (FAILED(result)){
		Com_DPrintf(S_COLOR_RED "Couldn't poll controller device\n");

		if (result == DIERR_NOTACQUIRED || result == DIERR_INPUTLOST){
			in_diController.device->SetCooperativeLevel(sys.hWndMain, DISCL_EXCLUSIVE | DISCL_FOREGROUND);

			in_diController.device->Acquire();
		}

		return;
	}

	// Generate axis events
	for (i = 0; i < in_diController.numAxes; i++){
		state = (&diDeviceState.lX)[i] - 32768;

		if (state < 0)
			IN_ControllerAxisEvent(i, state * (1.0f / 32768.0f));
		else if (state > 0)
			IN_ControllerAxisEvent(i, state * (1.0f / 32767.0f));
		else
			IN_ControllerAxisEvent(i, 0.0f);
	}

	// Generate POV events
	for (i = 0; i < in_diController.numPOVs; i++){
		state = 0;

		if (diDeviceState.rgdwPOV[i] >= 0 && diDeviceState.rgdwPOV[i] <= 6750)
			state |= BIT(0);
		if (diDeviceState.rgdwPOV[i] >= 2250 && diDeviceState.rgdwPOV[i] <= 15750)
			state |= BIT(1);
		if (diDeviceState.rgdwPOV[i] >= 11250 && diDeviceState.rgdwPOV[i] <= 24750)
			state |= BIT(2);
		if (diDeviceState.rgdwPOV[i] >= 20250 && diDeviceState.rgdwPOV[i] <= 33750)
			state |= BIT(3);
		if (diDeviceState.rgdwPOV[i] >= 29250 && diDeviceState.rgdwPOV[i] <= 36000)
			state |= BIT(0);

		if (in_diController.povState[i] != state){
			for (j = 0; j < 4; j++){
				if (!(in_diController.povState[i] & BIT(j)) && (state & BIT(j)))
					Key_Event(K_CTRL_POV1 + (i << 2) + j, com_frameTime, true);

				if ((in_diController.povState[i] & BIT(j)) && !(state & BIT(j)))
					Key_Event(K_CTRL_POV1 + (i << 2) + j, com_frameTime, false);
			}

			in_diController.povState[i] = state;
		}
	}

	// Generate button events
	state = 0;

	for (i = 0; i < in_diController.numButtons; i++){
		if (diDeviceState.rgbButtons[i] & 0x80)
			state |= BIT(i);
	}

	if (in_diController.buttonState != state){
		for (i = 0; i < in_diController.numButtons; i++){
			if (!(in_diController.buttonState & BIT(i)) && (state & BIT(i)))
				Key_Event(K_CTRL_BUTTON1 + i, com_frameTime, true);

			if ((in_diController.buttonState & BIT(i)) && !(state & BIT(i)))
				Key_Event(K_CTRL_BUTTON1 + i, com_frameTime, false);
		}

		in_diController.buttonState = state;
	}
}

/*
 ==================
 IN_InitDIController
 ==================
*/
static void IN_InitDIController (const GUID guid){

	DIDEVCAPS		diDevCaps;
	DIDATAFORMAT	diDataFormat = {sizeof(DIDATAFORMAT), sizeof(DIOBJECTDATAFORMAT), DIDF_ABSAXIS, DI_CONTROLLER_DATA_SIZE, DI_CONTROLLER_NUM_OBJECTS, in_diControllerObjects};
	DIPROPRANGE		diPropRange = {{sizeof(DIPROPRANGE), sizeof(DIPROPHEADER), 0, DIPH_DEVICE}, 0, 65535};
	HRESULT			result;

	if (!in_diInitialized){
		Com_Printf("...controller input is not available\n");
		return;
	}

	// Create the device
	result = in_diObject->CreateDevice(guid, &in_diController.device, NULL);
	if (FAILED(result)){
		Com_Printf("...couldn't create controller device\n");
		return;
	}

	// Get the device capabilities
	diDevCaps.dwSize = sizeof(DIDEVCAPS);

	result = in_diController.device->GetCapabilities(&diDevCaps);
	if (FAILED(result)){
		Com_Printf("...couldn't get controller device capabilities\n");

		in_diController.device->Release();
		in_diController.device = NULL;

		return;
	}

	// Set the data format
	result = in_diController.device->SetDataFormat(&diDataFormat);
	if (FAILED(result)){
		Com_Printf("...couldn't set controller device data format\n");

		in_diController.device->Release();
		in_diController.device = NULL;

		return;
	}

	// Set the axis range
	result = in_diController.device->SetProperty(DIPROP_RANGE, &diPropRange.diph);
	if (FAILED(result)){
		Com_Printf("...couldn't set controller device axis range\n");

		in_diController.device->Release();
		in_diController.device = NULL;

		return;
	}

	// Set the cooperative level
	result = in_diController.device->SetCooperativeLevel(sys.hWndMain, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
	if (FAILED(result))
		Com_DPrintf("...couldn't set controller device cooperative level\n");

	// Acquire the device
	result = in_diController.device->Acquire();
	if (FAILED(result))
		Com_DPrintf("...couldn't acquire controller device\n");

	// The controller device is now fully initialized
	in_diController.initialized = true;
	in_diController.active = true;

	in_diController.numAxes = diDevCaps.dwAxes;
	if (in_diController.numAxes > DI_CONTROLLER_MAX_AXES)
		in_diController.numAxes = DI_CONTROLLER_MAX_AXES;

	in_diController.numPOVs = diDevCaps.dwPOVs;
	if (in_diController.numPOVs > DI_CONTROLLER_MAX_POVS)
		in_diController.numPOVs = DI_CONTROLLER_MAX_POVS;

	in_diController.numButtons = diDevCaps.dwButtons;
	if (in_diController.numButtons > DI_CONTROLLER_MAX_BUTTONS)
		in_diController.numButtons = DI_CONTROLLER_MAX_BUTTONS;

	Com_Printf("...controller initialized (%i axes, %i POVs, %i buttons)\n", in_diController.numAxes, in_diController.numPOVs, in_diController.numButtons);
}

/*
 ==================
 IN_ShutdownDIController
 ==================
*/
static void IN_ShutdownDIController (){

	if (!in_diController.initialized)
		return;

	// Clear state
	IN_ClearDIControllerState();

	// Unacquire and release the device
	if (in_diController.device){
		in_diController.device->Unacquire();
		in_diController.device->Release();
		in_diController.device = NULL;
	}

	Mem_Fill(&in_diController, 0, sizeof(diController_t));
}


/*
 ==============================================================================

 XINPUT

 ==============================================================================
*/

typedef void				(WINAPI * XINPUTENABLE)(BOOL);
typedef DWORD				(WINAPI * XINPUTGETBATTERYINFORMATION)(DWORD, BYTE, PXINPUT_BATTERY_INFORMATION);
typedef DWORD				(WINAPI * XINPUTGETCAPABILITIES)(DWORD, DWORD, PXINPUT_CAPABILITIES);
typedef DWORD				(WINAPI * XINPUTGETDSOUNDAUDIODEVICEGUIDS)(DWORD, GUID *, GUID *);
typedef DWORD				(WINAPI * XINPUTGETKEYSTROKE)(DWORD, DWORD, PXINPUT_KEYSTROKE);
typedef DWORD				(WINAPI * XINPUTGETSTATE)(DWORD, PXINPUT_STATE);
typedef DWORD				(WINAPI * XINPUTSETSTATE)(DWORD, PXINPUT_VIBRATION);

static void					(WINAPI * pXInputEnable)(BOOL enable);
static DWORD				(WINAPI * pXInputGetBatteryInformation)(DWORD dwUserIndex, BYTE devType, PXINPUT_BATTERY_INFORMATION pBatteryInformation);
static DWORD				(WINAPI * pXInputGetCapabilities)(DWORD dwUserIndex, DWORD dwFlags, PXINPUT_CAPABILITIES pCapabilities);
static DWORD				(WINAPI * pXInputGetDSoundAudioDeviceGuids)(DWORD dwUserIndex, GUID *pDSoundRenderGuid, GUID *pDSoundCaptureGuid);
static DWORD				(WINAPI * pXInputGetKeystroke)(DWORD dwUserIndex, DWORD dwReserved, PXINPUT_KEYSTROKE pKeyStroke);
static DWORD				(WINAPI * pXInputGetState)(DWORD dwUserIndex, PXINPUT_STATE pState);
static DWORD				(WINAPI * pXInputSetState)(DWORD dwUserIndex, PXINPUT_VIBRATION pVibration);

static HMODULE				in_xiLibrary;
static bool					in_xiInitialized;


/*
 ==================
 IN_InitXInput
 ==================
*/
static void IN_InitXInput (){

	controller_t		*controller;
	XINPUT_CAPABILITIES	xiCapabilities;
	int					count = 0;
	int					i;

	Com_Printf("Initializing XInput\n");

	// Load the XInput DLL
	Com_Printf("...calling LoadLibrary( '%s' ): ", XINPUT_DLL);

	if ((in_xiLibrary = LoadLibrary(XINPUT_DLL)) == NULL){
		Com_Printf("failed\n");
		return;
	}

	Com_Printf("succeeded\n");

	// Get the function pointers
	if ((pXInputEnable = (XINPUTENABLE)GetProcAddress(in_xiLibrary, "XInputEnable")) == NULL){
		Com_Printf("...failed to get 'XInputEnable' procedure address\n");

		FreeLibrary(in_xiLibrary);
		in_xiLibrary = NULL;

		return;
	}

	if ((pXInputGetBatteryInformation = (XINPUTGETBATTERYINFORMATION)GetProcAddress(in_xiLibrary, "XInputGetBatteryInformation")) == NULL){
		Com_Printf("...failed to get 'XInputGetBatteryInformation' procedure address\n");

		FreeLibrary(in_xiLibrary);
		in_xiLibrary = NULL;

		return;
	}

	if ((pXInputGetCapabilities = (XINPUTGETCAPABILITIES)GetProcAddress(in_xiLibrary, "XInputGetCapabilities")) == NULL){
		Com_Printf("...failed to get 'XInputGetCapabilities' procedure address\n");

		FreeLibrary(in_xiLibrary);
		in_xiLibrary = NULL;

		return;
	}

	if ((pXInputGetDSoundAudioDeviceGuids = (XINPUTGETDSOUNDAUDIODEVICEGUIDS)GetProcAddress(in_xiLibrary, "XInputGetDSoundAudioDeviceGuids")) == NULL){
		Com_Printf("...failed to get 'XInputGetDSoundAudioDeviceGuids' procedure address\n");

		FreeLibrary(in_xiLibrary);
		in_xiLibrary = NULL;

		return;
	}

	if ((pXInputGetKeystroke = (XINPUTGETKEYSTROKE)GetProcAddress(in_xiLibrary, "XInputGetKeystroke")) == NULL){
		Com_Printf("...failed to get 'XInputGetKeystroke' procedure address\n");

		FreeLibrary(in_xiLibrary);
		in_xiLibrary = NULL;

		return;
	}

	if ((pXInputGetState = (XINPUTGETSTATE)GetProcAddress(in_xiLibrary, "XInputGetState")) == NULL){
		Com_Printf("...failed to get 'XInputGetState' procedure address\n");

		FreeLibrary(in_xiLibrary);
		in_xiLibrary = NULL;

		return;
	}

	if ((pXInputSetState = (XINPUTSETSTATE)GetProcAddress(in_xiLibrary, "XInputSetState")) == NULL){
		Com_Printf("...failed to get 'XInputSetState' procedure address\n");

		FreeLibrary(in_xiLibrary);
		in_xiLibrary = NULL;

		return;
	}

	// Enumerate available controllers
	for (i = 0; i < XUSER_MAX_COUNT; i++){
		// Try to get the device capabilities
		if (pXInputGetCapabilities(i, XINPUT_FLAG_GAMEPAD, &xiCapabilities) != ERROR_SUCCESS)
			continue;

		// Add it
		if (in_numControllers == MAX_CONTROLLERS)
			break;

		controller = &in_controllers[in_numControllers++];

		// Fill it in
		Str_SPrintf(controller->name, sizeof(controller->name), "Xbox 360 Controller for Windows #%i", i+1);
		controller->x360 = true;
		controller->numAxes = 4;
		controller->numPOVs = 0;
		controller->numButtons = 16;
		controller->userIndex = i;

		// Increment counter
		count++;
	}

	if (count)
		Com_Printf("...%i controllers found\n", count);
	else
		Com_Printf("...no controllers found\n");

	// XInput fully initialized
	in_xiInitialized = true;
}

/*
 ==================
 IN_ShutdownXInput
 ==================
*/
static void IN_ShutdownXInput (){

	if (!in_xiInitialized)
		return;

	Com_Printf("Shutting down XInput\n");

	if (in_xiLibrary){
		Com_Printf("...unloading XInput DLL\n");

		FreeLibrary(in_xiLibrary);
		in_xiLibrary = NULL;
	}

	in_xiInitialized = false;
}


/*
 ==============================================================================

 XINPUT CONTROLLER

 ==============================================================================
*/

struct xiController_t {
	bool					initialized;
	bool					active;

	DWORD					device;
	DWORD					packet;

	int						numAxes;
	int						numButtons;

	bool					dpadUpState;
	bool					dpadDownState;
	bool					dpadLeftState;
	bool					dpadRightState;
	bool					startState;
	bool					backState;
	bool					leftThumbState;
	bool					rightThumbState;
	bool					leftShoulderState;
	bool					rightShoulderState;
	bool					leftTriggerState;
	bool					rightTriggerState;
	bool					aState;
	bool					bState;
	bool					xState;
	bool					yState;
};

static xiController_t		in_xiController;


/*
 ==================
 IN_ClearXIControllerState
 ==================
*/
static void IN_ClearXIControllerState (){

	if (!in_xiController.initialized)
		return;

	// Clear axes
	IN_ClearControllerAxes();

	// Clear button state
	if (in_xiController.dpadUpState){
		in_xiController.dpadUpState = false;

		Key_Event(K_X360_DPAD_UP, 0, false);
	}

	if (in_xiController.dpadDownState){
		in_xiController.dpadDownState = false;

		Key_Event(K_X360_DPAD_DOWN, 0, false);
	}

	if (in_xiController.dpadLeftState){
		in_xiController.dpadLeftState = false;

		Key_Event(K_X360_DPAD_LEFT, 0, false);
	}

	if (in_xiController.dpadRightState){
		in_xiController.dpadRightState = false;

		Key_Event(K_X360_DPAD_RIGHT, 0, false);
	}

	if (in_xiController.startState){
		in_xiController.startState = false;

		Key_Event(K_X360_START, 0, false);
	}

	if (in_xiController.backState){
		in_xiController.backState = false;

		Key_Event(K_X360_BACK, 0, false);
	}

	if (in_xiController.leftThumbState){
		in_xiController.leftThumbState = false;

		Key_Event(K_X360_LEFT_THUMB, 0, false);
	}

	if (in_xiController.rightThumbState){
		in_xiController.rightThumbState = false;

		Key_Event(K_X360_RIGHT_THUMB, 0, false);
	}

	if (in_xiController.leftShoulderState){
		in_xiController.leftShoulderState = false;

		Key_Event(K_X360_LEFT_SHOULDER, 0, false);
	}

	if (in_xiController.rightShoulderState){
		in_xiController.rightShoulderState = false;

		Key_Event(K_X360_RIGHT_SHOULDER, 0, false);
	}

	if (in_xiController.leftTriggerState){
		in_xiController.leftTriggerState = false;

		Key_Event(K_X360_LEFT_TRIGGER, 0, false);
	}

	if (in_xiController.rightTriggerState){
		in_xiController.rightTriggerState = false;

		Key_Event(K_X360_RIGHT_TRIGGER, 0, false);
	}

	if (in_xiController.aState){
		in_xiController.aState = false;

		Key_Event(K_X360_A, 0, false);
	}

	if (in_xiController.bState){
		in_xiController.bState = false;

		Key_Event(K_X360_B, 0, false);
	}

	if (in_xiController.xState){
		in_xiController.xState = false;

		Key_Event(K_X360_X, 0, false);
	}

	if (in_xiController.yState){
		in_xiController.yState = false;

		Key_Event(K_X360_Y, 0, false);
	}
}

/*
 ==================
 IN_ActivateXIController
 ==================
*/
static void IN_ActivateXIController (){

	if (!in_xiController.initialized)
		return;

	if (in_xiController.active)
		return;
	in_xiController.active = true;

	// Enable the device
	pXInputEnable(TRUE);
}

/*
 ==================
 IN_DeactivateXIController
 ==================
*/
static void IN_DeactivateXIController (){

	if (!in_xiController.initialized)
		return;

	if (!in_xiController.active)
		return;
	in_xiController.active = false;

	// Clear state
	IN_ClearXIControllerState();

	// Disable the device
	pXInputEnable(FALSE);
}

/*
 ==================
 IN_PollXIController
 ==================
*/
static void IN_PollXIController (){

	XINPUT_STATE	xiState;
	bool			state;

	if (!in_xiController.initialized)
		return;

	// Temporarily deactivate if we don't have the focus
	if (!sys.hWndMain || !sys.isActive){
		IN_DeactivateXIController();
		return;
	}

	// Activate if needed
	IN_ActivateXIController();

	// Poll the device
	if (pXInputGetState(in_xiController.device, &xiState) != ERROR_SUCCESS){
		Com_DPrintf(S_COLOR_RED "Couldn't poll controller device\n");
		return;
	}

	if (in_xiController.packet == xiState.dwPacketNumber)
		return;
	in_xiController.packet = xiState.dwPacketNumber;

	// Generate axis events
	if (xiState.Gamepad.sThumbLX < 0)
		IN_ControllerAxisEvent(0, xiState.Gamepad.sThumbLX * (1.0f / 32768.0f));
	else if (xiState.Gamepad.sThumbLX > 0)
		IN_ControllerAxisEvent(0, xiState.Gamepad.sThumbLX * (1.0f / 32767.0f));
	else
		IN_ControllerAxisEvent(0, 0.0f);

	if (xiState.Gamepad.sThumbLY < 0)
		IN_ControllerAxisEvent(1, xiState.Gamepad.sThumbLY * (1.0f / 32768.0f));
	else if (xiState.Gamepad.sThumbLY > 0)
		IN_ControllerAxisEvent(1, xiState.Gamepad.sThumbLY * (1.0f / 32767.0f));
	else
		IN_ControllerAxisEvent(1, 0.0f);

	if (xiState.Gamepad.sThumbRX < 0)
		IN_ControllerAxisEvent(2, xiState.Gamepad.sThumbRX * (1.0f / 32768.0f));
	else if (xiState.Gamepad.sThumbRX > 0)
		IN_ControllerAxisEvent(2, xiState.Gamepad.sThumbRX * (1.0f / 32767.0f));
	else
		IN_ControllerAxisEvent(2, 0.0f);

	if (xiState.Gamepad.sThumbRY < 0)
		IN_ControllerAxisEvent(3, xiState.Gamepad.sThumbRY * (1.0f / 32768.0f));
	else if (xiState.Gamepad.sThumbRY > 0)
		IN_ControllerAxisEvent(3, xiState.Gamepad.sThumbRY * (1.0f / 32767.0f));
	else
		IN_ControllerAxisEvent(3, 0.0f);

	// Generate button events
	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP);

	if (in_xiController.dpadUpState != state){
		in_xiController.dpadUpState = state;

		Key_Event(K_X360_DPAD_UP, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN);

	if (in_xiController.dpadDownState != state){
		in_xiController.dpadDownState = state;

		Key_Event(K_X360_DPAD_DOWN, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT);

	if (in_xiController.dpadLeftState != state){
		in_xiController.dpadLeftState = state;

		Key_Event(K_X360_DPAD_LEFT, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT);

	if (in_xiController.dpadRightState != state){
		in_xiController.dpadRightState = state;

		Key_Event(K_X360_DPAD_RIGHT, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_START);

	if (in_xiController.startState != state){
		in_xiController.startState = state;

		Key_Event(K_X360_START, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_BACK);

	if (in_xiController.backState != state){
		in_xiController.backState = state;

		Key_Event(K_X360_BACK, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB);

	if (in_xiController.leftThumbState != state){
		in_xiController.leftThumbState = state;

		Key_Event(K_X360_LEFT_THUMB, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB);

	if (in_xiController.rightThumbState != state){
		in_xiController.rightThumbState = state;

		Key_Event(K_X360_RIGHT_THUMB, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER);

	if (in_xiController.leftShoulderState != state){
		in_xiController.leftShoulderState = state;

		Key_Event(K_X360_LEFT_SHOULDER, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER);

	if (in_xiController.rightShoulderState != state){
		in_xiController.rightShoulderState = state;

		Key_Event(K_X360_RIGHT_SHOULDER, com_frameTime, state);
	}

	state = (xiState.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD);

	if (in_xiController.leftTriggerState != state){
		in_xiController.leftTriggerState = state;

		Key_Event(K_X360_LEFT_TRIGGER, com_frameTime, state);
	}

	state = (xiState.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD);

	if (in_xiController.rightTriggerState != state){
		in_xiController.rightTriggerState = state;

		Key_Event(K_X360_RIGHT_TRIGGER, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_A);

	if (in_xiController.aState != state){
		in_xiController.aState = state;

		Key_Event(K_X360_A, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_B);

	if (in_xiController.bState != state){
		in_xiController.bState = state;

		Key_Event(K_X360_B, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_X);

	if (in_xiController.xState != state){
		in_xiController.xState = state;

		Key_Event(K_X360_X, com_frameTime, state);
	}

	state = (xiState.Gamepad.wButtons & XINPUT_GAMEPAD_Y);

	if (in_xiController.yState != state){
		in_xiController.yState = state;

		Key_Event(K_X360_Y, com_frameTime, state);
	}
}

/*
 ==================
 IN_InitXIController
 ==================
*/
static void IN_InitXIController (DWORD userIndex){

	XINPUT_CAPABILITIES	xiCapabilities;

	if (!in_xiInitialized){
		Com_Printf("...controller input is not available\n");
		return;
	}

	// Get the device capabilities
	if (pXInputGetCapabilities(userIndex, XINPUT_FLAG_GAMEPAD, &xiCapabilities) != ERROR_SUCCESS){
		Com_Printf("...couldn't get controller device capabilities\n");
		return;
	}

	// Enable the device
	pXInputEnable(TRUE);

	// The controller device is now fully initialized
	in_xiController.initialized = true;
	in_xiController.active = true;

	in_xiController.device = userIndex;
	in_xiController.packet = 0xFFFFFFFF;

	in_xiController.numAxes = 4;
	in_xiController.numButtons = 16;

	Com_Printf("...controller initialized (%i axes, %i buttons)\n", in_xiController.numAxes, in_xiController.numButtons);
}

/*
 ==================
 IN_ShutdownXIController
 ==================
*/
static void IN_ShutdownXIController (){

	if (!in_xiController.initialized)
		return;

	// Clear state
	IN_ClearXIControllerState();

	// Disable the device
	pXInputEnable(FALSE);

	Mem_Fill(&in_xiController, 0, sizeof(xiController_t));
}


// ============================================================================


/*
 ==================
 IN_ListControllers_f
 ==================
*/
static void IN_ListControllers_f (){

	controller_t	*controller;
	int				i;

	for (i = 0, controller = in_controllers; i < in_numControllers; i++, controller++)
		Com_Printf("%s (%i axes, %i POVs, %i buttons)\n", controller->name, controller->numAxes, controller->numPOVs, controller->numButtons);

	Com_Printf("--------------------\n");
	Com_Printf("%i controllers available\n", in_numControllers);
}

/*
 ==================
 IN_FindController
 ==================
*/
static controller_t *IN_FindController (){

	controller_t	*controller;
	int				best = -1;
	int				i;

	// If a device name is specified, try to find that specific controller
	if (c_deviceName->value[0]){
		for (i = 0, controller = in_controllers; i < in_numControllers; i++, controller++){
			if (!Str_ICompare(controller->name, c_deviceName->value))
				return controller;
		}

		return NULL;
	}

	// Otherwise find the best one
	for (i = 0, controller = in_controllers; i < in_numControllers; i++, controller++){
		// If we don't have a controller yet, use this one
		if (best == -1){
			best = i;
			continue;
		}

		// Always favor Xbox 360 controllers
		if (controller->x360 && !in_controllers[best].x360){
			best = i;
			continue;
		}

		// Always favor controllers with a higher number of axes
		if (controller->numAxes > in_controllers[best].numAxes){
			best = i;
			continue;
		}

		// Always favor controllers with a higher number of POV hat switches
		if (controller->numPOVs > in_controllers[best].numPOVs){
			best = i;
			continue;
		}

		// Always favor controllers with a higher number of buttons
		if (controller->numButtons > in_controllers[best].numButtons){
			best = i;
			continue;
		}
	}

	if (best == -1)
		return NULL;

	return &in_controllers[best];
}

/*
 ==================
 IN_ClearDeviceStates
 ==================
*/
void IN_ClearDeviceStates (){

	// Clear mouse state
	IN_ClearDIMouseState();

	// Clear controller state
	IN_ClearDIControllerState();
	IN_ClearXIControllerState();
}

/*
 ==================
 IN_PollDevices
 ==================
*/
void IN_PollDevices (){

	// Poll mouse
	IN_PollDIMouse();

	// Poll controller
	IN_PollDIController();
	IN_PollXIController();
}

/*
 ==================
 IN_ActivateDevices
 ==================
*/
void IN_ActivateDevices (){

	// Activate mouse
	IN_ActivateDIMouse();

	// Activate controller
	IN_ActivateDIController();
	IN_ActivateXIController();
}

/*
 ==================
 IN_DeactivateDevices
 ==================
*/
void IN_DeactivateDevices (){

	// Deactivate controller
	IN_DeactivateXIController();
	IN_DeactivateDIController();

	// Deactivate mouse
	IN_DeactivateDIMouse();
}

/*
 ==================
 IN_InitDevices
 ==================
*/
void IN_InitDevices (){

	controller_t	*controller;

	// Add commands
	Cmd_AddCommand("listControllers", IN_ListControllers_f, "Lists available controllers", NULL);

	// Initialize DirectInput
	IN_InitDirectInput();

	// Initialize XInput
	IN_InitXInput();

	// Initialize the devices
	Com_Printf("Initializing devices\n");

	// Initialize mouse
	if (!in_mouse->integerValue)
		Com_Printf("...mouse input is disabled\n");
	else
		IN_InitDIMouse();

	// Initialize controller
	if (!in_controller->integerValue)
		Com_Printf("...controller input is disabled\n");
	else {
		controller = IN_FindController();

		if (!controller)
			Com_Printf("...couldn't find controller\n");
		else {
			if (controller->x360)
				IN_InitXIController(controller->userIndex);
			else
				IN_InitDIController(controller->guid);
		}
	}
}

/*
 ==================
 IN_ShutdownDevices
 ==================
*/
void IN_ShutdownDevices (){

	// Remove commands
	Cmd_RemoveCommand("listControllers");

	// Shutdown controller
	IN_ShutdownXIController();
	IN_ShutdownDIController();

	// Shutdown mouse
	IN_ShutdownDIMouse();

	// Shutdown XInput
	IN_ShutdownXInput();

	// Shutdown DirectInput
	IN_ShutdownDirectInput();

	// Clear controller list
	Mem_Fill(in_controllers, 0, sizeof(in_controllers));

	in_numControllers = 0;
}